class Grid
    constructor: (@table, @options) ->
        @id = @table.prop "id"

        @table.addClass "rhd-grid table"

        @container = $ "<div>", { class: "rhd-grid-container" }
        @container.insertAfter @table
        @container.wrapInner @table
        @container.hide()

        if @options.responsive
            response = $ "<div>", { class: "table-responsive" }
            response.appendTo @container
            response.wrapInner @table

        @init()

    init: ->
        @initColumns()
        @make()
        @data = new GridData @, @options.data
        @table.data "rhd.grid", @
        @table.triggerHandler "init.rhd.grid"

    initColumns: ->
        @columns = []
        @columns.push new GridColumn { active: true, multiSelect: true, header: "multiselect", renderer: "multiselect", order: 0 } if @options.multiSelect

        i = 0
        for col in @options.columns
            col.order = ++i
            @columns.push new GridColumn col

    refresh: ->
        @data.loadData()

    loadData: (url, postData, method) ->
        @data.loadData url, postData, method

    ready: ->
        @hideDialog()
        @remake()
        @table.triggerHandler "ready.rhd.grid"

    make: ->
        @makeDialog()
        @makeHeader()
        @makeBody()

    remake: ->
        @remakeHeader()
        @remakeContent()

    remakeContent: ->
        @remakeBody()
        @remakeComponents()
        @table.triggerHandler "remake.rhd.grid"

    makeDialog: ->
        @dialog = $ "<div>", { class: "rhd-grid-dialog alert alert-info" }
        @dialog.insertBefore @container
        @dialog.hide()

    showDialog: (msg) ->
        @container.hide()
        @dialog.html msg
        @dialog.show()

    hideDialog: ->
        @dialog.hide()
        @container.show()

    makeHeader: ->
        @thead = $ "<thead>"
        @thead.appendTo @table

    remakeHeader: ->
        @thead.html ""

        header = $ "<tr>"
        header.appendTo @thead

        for col in @getActiveColumns()
            th = $ "<th>"
            th.appendTo header
            @makeHeaderCell th, col
        
    makeHeaderCell: (th, col) ->
        renderer = $.rhd.grid.header._default
        options = col.options.headerData || {}

        if $.isFunction col.options.header
            renderer = col.options.header
        else if col.options.header?
            renderer = $.rhd.grid.header[col.options.header]
            renderer = eval(col.options.header) if not renderer?

        renderer.call @, th, col, options

    makeBody: ->
        @tbody = $ "<tbody>"
        @tbody.appendTo @table
        @

    remakeBody: =>
        @tbody.html ""
        @renderLine row for row in @pageRows()

    remakeComponents: ->
        @table.triggerHandler "summary.nav.rhd.grid", @makeSummary()
        @table.triggerHandler "buttons.nav.rhd.grid", @makeButtons()
        @table.triggerHandler "options.nav.rhd.grid", @makeOptions()

    renderLine: (row) =>
        tr = $ "<tr>"
        tr.appendTo @tbody
        @renderCell tr, row, col for col in @getActiveColumns()

    renderCell: (tr, row, col) =>
        td = $ "<td>"
        td.appendTo tr

        renderer = $.rhd.grid.renderer._default
        options = col.options.rendererData || {}

        if $.isFunction col.options.renderer
            renderer = col.options.renderer
        else if col.options.renderer?
            renderer = $.rhd.grid.renderer[col.options.renderer]
            renderer = eval(col.options.renderer) if not renderer?

        value = col.getFormattedValue row

        renderer.call @, td, value, col, row, options
        td

    makeIconSort: (column) ->
        return "" if not column.sort? or not column.sort.type? or column.sort.type is GridTypes.sortType.none

        span = $ "<span>"
        icon = if column.sort.type is GridTypes.sortType.asc then "glyphicon glyphicon-chevron-up" else "glyphicon glyphicon-chevron-down"
        span.append $ "<i>", { class: icon }
        span.append column.sort.order + 1

    toggleSort: (column, group) ->
        type = column.sort.type
        switch type
            when GridTypes.sortType.asc then type = GridTypes.sortType.desc
            when GridTypes.sortType.desc then type = GridTypes.sortType.asc
            else
                type = GridTypes.sortType.asc

        @sortAndGroup column, type, group

    sortAndGroup: (column, type, group) ->
        type = GridTypes.sortType.none if type != GridTypes.sortType.asc and type != GridTypes.sortType.desc

        order = 0
        if group
            if not column.sort.order?
                lastOrder = -1
                lastOrder = col.sort.order for col in @getColumns() when col.sort? and lastOrder < col.sort.order
                order = lastOrder + 1
            else
                order = column.sort.order
        else
            @removeSort()

        column.sort.type = type
        column.sort.order = order

        @sort()
        @remake()

    removeSort: ->
        for col in @getColumns() when col.sort?
            col.sort.type = GridTypes.sortType.none
            col.sort.order = null

    sort: ->
        @sortData @data.rows

    sortData: (data) ->
        sorted = []
        sorted.push col for col in @getColumns() when col.sort? and col.sort.type and col.sort.type != GridTypes.sortType.none

        if sorted.length > 0
          sorted.sort (a, b) ->
            if a.sort.order > b.sort.order then 1 else -1

          data.sort (a, b) =>
            @sortRow a, b, sorted

    sortRow: (a, b, columns) ->
        result = -2
        for col in columns
            result = @sortCol a, b, col
            return result if result != 0
        result

    sortCol: (a, b, col) ->
        aValue = col.getModelSortValue a
        bValue = col.getModelSortValue b

        sorter = $.rhd.grid.sorter[col.sort.dataType] || $.rhd.grid.sorter.string

        if $.isFunction col.options.sort.sorter
            sorter = col.options.sort.sorter
        else if col.options.sort.sorter?
            sorter = $.rhd.grid.sorter[col.options.sort.sorter]
            sorter = eval(col.options.sort.sorter) if not sorter?

        if col.sort.type == GridTypes.sortType.asc
            sorter.call @, aValue, bValue
        else
            sorter.call @, bValue, aValue

    pageRows: ->
        return [] if not @data?.rows
        return @data.rows if not @options.paging?
        @options.paging.currentPage = 1 if not @options.paging.currentPage?
        index = (@options.paging.currentPage - 1) * @options.paging.size
        @data.rows.slice index, index + @options.paging.size

    getMultiSelect: () ->
        multiselect = null
        for col in @getColumns() when col.multiSelect
            multiselect = col
        multiselect

    getColumn: (name) ->
        column = null
        column = col for col in @columns when col.name? and col.name is name
        column

    getColumns: ->
        @columns

    getActiveColumns: ->
        columns = []
        columns.push col for col in @getSortedColumns() when col.active
        columns

    getSortedColumns: ->
        columns = []
        columns.push col for col in @columns
        columns.sort (a, b)->
            if a.order > b.order then 1 else -1
        columns

    makeSummary: ->
        total = @data.rows.length
        if total > 0
            first = if @options.paging.currentPage is 1 then 1 else (@options.paging.size * (@options.paging.currentPage - 1)) + 1
            last = if first + @options.paging.size > total then total else (first + @options.paging.size) - 1
        else
            first = 0
            last = 0
        $.rhd.grid.locale.paging.summary.format first, last, total

    makeButtons: =>
        group = $ "<span>", { class: "btn-group" }

        prev = $ "<a>", { class: "btn btn-sm btn-default", title: $.rhd.grid.locale.paging.prev }
        prev.addClass "disabled" if @getTotalPages() is 0 or @options.paging.currentPage is 1
        prev.append $ "<i>", { class: "glyphicon glyphicon-chevron-left" }
        prev.appendTo group
        prev.click =>
            @prevPage()

        next = $ "<a>", { class: "btn btn-sm btn-default", title: $.rhd.grid.locale.paging.next }
        next.addClass "disabled" if @getTotalPages() is 0 or @options.paging.currentPage is @getTotalPages()
        next.append $ "<i>", { class: "glyphicon glyphicon-chevron-right" }
        next.appendTo group
        next.click =>
            @nextPage()

        group

    makeOptions: =>
        group = $ "<div>", { class: "btn-group" }

        btnRefresh = $ "<a>", { class: "btn btn-sm btn-default" }
        btnRefresh.append $ "<i>", { class: "glyphicon glyphicon-repeat" }
        btnRefresh.appendTo group
        btnRefresh.click =>
            @refresh()

        btnOptions = $ "<a>", { class: "btn btn-sm btn-default dropdown-toggle", "data-toggle": "dropdown" }
        btnOptions.append $ "<span>", { class: "caret" }
        btnOptions.appendTo group

        btnOptionsMenu = $ "<ul>", { class: "dropdown-menu" }
        btnOptionsMenu.appendTo group

        btnOptionsMenu.append $("<li/>", { class: "dropdown-header" }).append $.rhd.grid.locale.paging.paging
        btnOptionsMenu.append $("<li/>").append($("<a/>", href: "#").append($.rhd.grid.locale.paging.first).click =>
            @firstPage()
            false
        )
        btnOptionsMenu.append $("<li/>").append($("<a/>", href: "#").append($.rhd.grid.locale.paging.last).click =>
            @lastPage()
            false
        )

        btnOptionsMenu.append $("<li>", { class: "dropdown-header" }).append $.rhd.grid.locale.paging.pageSize
        for size in @options.paging.options
            li = $ "<li>"
            li.appendTo btnOptionsMenu

            a = $ "<a>", { href: "#" }
            a.append size
            a.appendTo li
            a.click (event) =>
                @setPageSize $(event.target).html()
                false

        @table.triggerHandler "item.options.nav.rhd.grid", btnOptionsMenu
        group

    firstPage: ->
        @options.paging.currentPage = 1
        @remakeContent()

    prevPage: ->
        @options.paging.currentPage-- if @options.paging.currentPage > 1
        @remakeContent()

    nextPage: ->
        @options.paging.currentPage++ if @options.paging.currentPage < @getTotalPages()
        @remakeContent()

    lastPage: ->
        @options.paging.currentPage = @getTotalPages()
        @remakeContent()

    setPage: (page) ->
        total = @getTotalPages()
        iPage = parseFloat page
        iPage = 0 if isNaN iPage
        currentPage = if iPage < 1 then 1 else if iPage > total then total else iPage
        @options.paging.currentPage = currentPage
        @remake()

    getTotalPages: ->
        Math.ceil @data.rows.length / @options.paging.size

    setPageSize: (value) ->
        @options.paging.currentPage = 1
        @options.paging.size = parseFloat value
        @remake()

    find: (word) ->
        @data.find word
        @options.paging.currentPage = 1
        @remake()

    pageChecked: ->
        multiSelect = @getMultiSelect()
        if multiSelect?
            for row in @pageRows()
                return false if not row[multiSelect.dataField]
        true

    checkPage: =>
        @checkUncheckPage true

    uncheckPage: =>
        @checkUncheckPage false

    checkUncheckPage: (checked) =>
        @checkUncheck @pageRows(), checked

    checkAll: =>
        @checkUncheckAll true

    uncheckAll: =>
        @checkUncheckAll false

    checkUncheckAll: (checked) =>
        @checkUncheck @data.rows, checked

    checkUncheck: (rows, checked) ->
        multiSelect = @getMultiSelect()
        if multiSelect?
            row[multiSelect.dataField] = checked for row in rows
            @remake()
            @updatedMultiSelect()

    checkData: (data) =>
        @checkUncheckData data, true

    unhcheckData: (data) =>
        @checkUncheckData data, false

    checkUncheckData: (data, checked) =>
        return if not data or data.length is 0

        multiSelect = @getMultiSelect()
        if multiSelect?
            properties = $.MEGetPropertyOfObject data[0]
            for row in @getData()
                for obj in data
                    equals = true
                    for prop in properties
                        if row[prop] != obj[prop]
                            equals = false
                            break
                    row[multiSelect.dataField] = checked if equals
            @remake()
            @updatedMultiSelect()

    invert: =>
        multiSelect = @getMultiSelect()
        if multiSelect?
            row[multiSelect.dataField] = !row[multiSelect.dataField] for row in @data.rows
            @remake()
            @updatedMultiSelect()

    updatedMultiSelect: ->
        @check.get(0).checked = @pageChecked() if @check?
        @table.triggerHandler "multiSelect.rhd.grid"

    getCheckedRows: =>
        rows = []
        multiSelect = @getMultiSelect()
        if multiSelect?
            rows.push row for row in @getData() when row[multiSelect.dataField]
        rows

    getData: () ->
        @data.getData()

    getModelData: (unformatted) ->
        data = []
        for row in @getData()
            model = {}
            for col in @getColumns()
                model[col.dataField] = if unformatted then col.getModelValue row else col.getFormattedValue row
            data.push model
        data

class GridData
    constructor: (@grid, options) ->
        {@url, @method, @postData, @init, @rows} = options
        @rows = [] if not @rows?
        @loadData() if @init

    loadData: (url, postData, method) ->
        @grid.options.paging.currentPage = 1 if @grid.options.paging
        @grid.showDialog $.rhd.grid.locale.loadText
        $.ajax
            type: method or @method
            url: url or @url
            dataType: 'json'
            data: postData or @postData
            success: @onLoadData

    onLoadData: (data, status, xhr) =>
        @setData if data? then data else []
        @grid.table.triggerHandler "loadComplete.rhd.grid", data, status, xhr

        if @rows.length is 0
            @grid.showDialog $.rhd.grid.locale.noRecord
            @grid.remakeComponents()
        else
            @prepareData()
            @grid.ready()

    getData: () ->
        return @allRows

    setData: (rows) ->
        @rows = rows || []
        @allRows = @rows
        @grid.sort()
        @grid.table.triggerHandler "data.rhd.grid"

    prepareData: ->
      for col in @grid.getColumns() when col.multiSelect and (not col.dataField? or col.dataField is "")
        col.dataField = "me_grid_sel"
        for row in @allRows
          row[col.dataField] = false

    find: (word) ->
        @rows = []
        columns = @grid.getActiveColumns()
        data = @allRows
        @rows.push row for row in data when @findHasValue row, word, columns
        @grid.table.triggerHandler "filter.rhd.grid"

    findHasValue: (row, word, columns) ->
        for col in columns
            return true if col.getFormattedValue(row).toLowerCase().search(word.toLowerCase()) >= 0
        false

class GridColumn
    constructor: (@options) ->
        {@name, @title, @dataField, @dataType, @active, @order, @sort, @multiSelect} = @options
        @dataType = @dataType || "string"
        @active = true unless @active?

    fill: (str, row) ->
        return "" if not str?
        str.replace /\{(.*?)\}/g, =>
            @getValue arguments[1], row

    getModelValue: (row) ->
        @getValue @dataField, row

    getModelSortValue: (row) ->
        @getValue @sort.dataField, row

    getFormattedValue: (row) ->
        formatter = $.rhd.grid.formatter[@dataType] || $.rhd.grid.formatter.string
        options = $.extend true, $.rhd.grid.format[@dataType], @options.formatterData

        if $.isFunction @options.formatter
            formatter = @options.formatter
        else if @options.formatter?
            formatter = $.rhd.grid.formatter[@options.formatter]
            formatter = eval(@options.formatter) if not formatter?

        value = formatter?.call @, @getModelValue(row), options
        value

    getValue: (dataField, value) ->
        $.rhd.grid.functions.getValue dataField, value

class GridTypes
    @sortType =
        none: "none"
        asc: "asc"
        desc: "desc"

class GridDefault extends Grid
    make: ->
        super()
        @makeNavBar()

    remake: ->
        super()
        @navBar.show()

    refresh: ->
        super()
        @quickSearch.val ''

    makeNavBar: ->
        @navBar = $ "<div>", { class: "simple-navbar" }
        @navBar.append @makeSearch()
        @navBar.append @makeNavBarItems()
        @navBar.prependTo @container
        @navBar.hide()

    makeSearch: =>
        @quickSearch = $ "<input>", { type: "text", class: "form-control input-sm", placeholder: $.rhd.grid.locale.quickSearch }
        @quickSearch.keypress (event)=>
            charCode = if event.which? then event.which else event.keyCode
            if charCode is 13
                @find @quickSearch.val()
            true

        @quickSearch

    makeNavBarItems: =>
        div = $ "<div>"

        @summary = $ "<span>", { class: "summary" }
        @summary.appendTo div
        @table.on "summary.nav.rhd.grid", (e, str) =>
            @summary.html str

        @buttonsGroup = $ "<span/>"
        @buttonsGroup.appendTo div
        @table.on "buttons.nav.rhd.grid", (e, btnGroup) =>
            @buttonsGroup.html btnGroup

        @optionsGroup = $ "<span/>"
        @optionsGroup.appendTo div
        @table.on "options.nav.rhd.grid", (e, btnGroup) =>
            $(btnGroup).addClass "pull-right options"
            @optionsGroup.html btnGroup

        div


# jQuery plugin

$.fn.extend
    jGrid: (options) ->
        defaults =
            responsive: true

        _isMethod = (typeof options is "string")
        _arguments = Array::slice.call(arguments, 1)

        _result = @
        @each ->
            table = $ @
            obj = table.data "rhd.grid"

            return if (obj? and not _isMethod)

            if (_isMethod)
                _result = obj[options](_arguments...)
            else
                options = $.extend defaults, options
                new GridDefault table, options
        _result